#! usr/bin/python
# -*- coding: utf-8 -*-


"""
Copyright (c): Marc Dubois                                                
contributors: Pierre Millard, Fabien Jourdan, Ludovic Cottret         
Fabien Letisse                                                        

m_dubois@insa-toulouse.fr                                             

This software is a computer program whose purpose is to treat data    
from Mass Spectrometry.                                               

This software is governed by the CeCILL license under French law and  
abiding by the rules of distribution of free software.  You can  use, 
modify and/ or redistribute the software under the terms of the CeCILL
license as circulated by CEA, CNRS and INRIA at the following URL     
"http://www.cecill.info".                                             

As a counterpart to the access to the source code and  rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty  and the software's author,  the holder of the
economic rights,  and the successive licensors  have only  limited    
liability.                                                            

In this respect, the user's attention is drawn to the risks associated
with loading,  using,  modifying and/or developing or reproducing the
software by the user in light of its specific status of free software,
that may mean  that it is complicated to manipulate,  and  that  also
therefore means  that it is reserved for developers  and  experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or 
data to be ensured and,  more generally, to use and operate it in the 
same conditions as regards security. 

The fact that you are presently reading this means that you have had
knowledge of the CeCILL license and that you accept its terms.
"""

import base64
import struct
import math
import re
import processing
import numpy as np
from operator import attrgetter

try:
	from pylab import *
except ImportError:"Pylab is not installed"




class AutoSlots(type):
	"""
	metaclass for handling slots
	"""
	def __new__(cls, name, bases, attrs):
		slots =attrs.get('__slots__', set())
		#slots.add('__dict__')
		
		for p in attrs['attributes']:
			slots.add(p)
		
		attrs['__slots__'] =tuple(slots)
		
		return type.__new__(cls, name, bases, attrs)


		
class PySpecBasis(object):
	
	__metaclass__ = AutoSlots
	attributes =()

	def __init__(self):
		object.__init__(self)
		
		
		
class MSn_trans (tuple):
	"""
	Transition multiple MS
	"""
	def __init__ (self, list_mass):
		tuple.__init__(self, sorted(list_mass))
	
	def __str__(self):pass
	
	def msDegree( self):
		return len(self)
	
	def maxMass(self):
		return max(self)

		
class MS2_trans (MSn_trans):#
	"""
	Transition 2
	"""
	
	def __init__(self, transition):
		MSn_trans.__init__(self, transition)
		if len(transition) >2:
			raise ValueError ("not a ms2 transition")
		if transition[1] > transition[0] :
			raise ValueError ("Error fragment' mass higher than precursor' mass")  
		
	#called when str(MS2_trans object) is called		
	def __str__(self):	
		return str(self.get_prec())+"/"+str(self.get_frag())
	
	
	def __eq__(self,transition_object):
		if not isinstance(transition_object, MS2_trans) or len(self) != len(transition_object):
			return False
		for value, value_ in zip(self, transition_object):
			if value != value_:
				return False
		return True
	
	def Precursor(self):
		return float(self[0])
	
	def fragmentedIon(self):
		return float(self[1])
	
	def floatingPrecision(self):pass	
		
		
class RawPeakList (list):
	"""
	List of peaks, allow iteration operation
	"""
	
	def __init__(self, peakList=[]):
		"""
		Constructor, derivating from list object
		"""
		list.__init__(self, peakList)
	
	def _gen_peaks(self):
		"""
		return a generator of peaks
		"""
		for peak in self:
			yield peak
	
	
	def __eq__(self, peaklist):#is_included_in
		"""
		replace == does not care of indexes
		"""
		if len(self) != len(peaklist): return False
		for peak in self._gen_peaks():
			i=0; found =False
			while not found and i < len(self):
				if peak == peaklist[i]:
					found =True
				else:
					i += 1
			if not found:
				return False
		return True
	
	def __neq__(self, peaklist):
		"""
		care of inclusion
		"""
		return not self == peaklist
	
	
	def add_peak(self, p):
		"""
		just test if p is a chromatoPeak object then append
		"""
		if not isinstance(p, chromatoPeak):
			raise TypeError, "RawPeakList object must be chromatoPeak"
		self.append(p)
	
	
	def peak_at_transition(self, trans):
		"""
		return a generator of peaks at the given transition
		"""
		for peak in self._gen_peaks():
			if trans == peak.get_trans_prec():
				yield peak
	
	def peaklist_at_trans(self, transition):
		"""
		return the list of peaks at the given transition
		"""
		return [peak for peak in self._gen_peaks() if trans == peak.get_trans_prec()]
		
	def peak (self, trans, rt):
		"""
		in theory this must return a unique value
		return a generator...
		"""
		for peak in self._gen_peaks():
			if peak.get_rt() == rt and peak.get_trans_prec()==trans:
				return peak
		return False
	
	
	def peak_at_rt(self, time):
		"""
		return a generator of peaks at the given transition
		"""
		for peak in self._gen_peaks():
			if peak.get_rt == time:
				yield peak
	
	
	def peaklist_at_rt(self, time):
		"""
		return the list of peak at the given time
		"""
		return [peak for peak in self._gen_peaks() if peak.get_rt()==time]
		
	
	def as_trans_list (self):
		"""
		return transition list
		"""
		return [str(p.get_trans_prec()) for p in self._gen_peaks()]
	
	def _gen_trans_list(self):
		for peak in self._gen_peaks():
			yield peak.get_trans_prec()
	
	def as_rt_list(self):
		"""
		return time list
		"""
		return[ p.get_rt() for p in self._gen_peaks()]
		
	def is_peak(self, mass, rt):
		for peak in self:
			if peak.get_rt()==rt and peak.get_trans_prec() == mass:
				return peak
		return False
	

class ClusterList(RawPeakList):
	"""
	handling fragment, and isotopic_cluster
	"""
	
	#attributes =('__valid', '__inter_corr', '__intra_corr')
	#__metaclass__ = AutoSlots
	
	def __init__(self, peaklist=[]):
		"""
		Constructor
		"""
		RawPeakList.__init__(self, peaklist)
		self.__valid ="NA"
		self.__inter_corr ="NA"
		self.__intra_corr ="NA"
		
	def set_intra_corr(self, cor):
		self.__intra_corr =cor
	
	def set_inter_corr(self, cor):
		self.__inter_corr = cor
		
	def get_inter_corr(self):
		return self.__inter_corr
	
	def get_intra_corr(self):
		return self.__intra_corr
		
	def is_empty(self):
		return len(self)==False
	
	def is_valid(self):
		return self.__valid
		
	def set_valid(self, bool):
		self.__valid =bool
		
	def size(self):
		return len(self) 	


class AbstractTypes(PySpecBasis):
	"""
	Abstract class for handling Spectrum, Chromatogram class
	"""
	
	attributes= ('t_min', 't_max', 'X', 'Y')
	
	def __init__(self, t_min=0, t_max=0, X = [], Y =[] ):
		"""
		Constructor
		"""
		
		if len(X) != len(Y):
			raise ValueError, "X and Y data points must have the same length"
				
		self.X = np.array(X)
		self.Y = np.array(Y)

	def getX(self): return self.X
	
	def getY(self):return self.Y
	
	def __getslice(self, x, x_):
		"""
		Emulation of the special method __getslice__ which need integers, got float instead (retention time)
		cannot use while beacuase of bug...
		"""
		x_match =list()
		y_match = list()
		for x_data, y_data in zip(self.X, self.Y):
			if x_data > x and x_data <x_:
				x_match.append(x_data)
				y_match.append(y_data)
		return x_match, y_match
			
		
	def __slice_generator(self, x, x_):
		"""
		return a generator
		"""
		for x_data, y_data in zip(self.X, self.Y):
			if x_data > x and x_data <x_:
				yield x_data, y_data
	
	def __data_generator(self):
		for data in zip(self.X, self.Y):
			yield data


	def Plot(self):
		raise NotImplementedError, "You must implement a plot function in your subclass"
		
	def t_min(self):
		return self.t_min
	
	def t_max(self):
		return self.t_max
	
	def set_t_max(self,time):
		if not isinstance(time, float): raise TypeError, "Attributes t_min and t_max must be float object, "+type(time)+" instead"
		self.t_max = t_max
	
	def set_t_min(self,time):
		if not isinstance(time, float): raise TypeError, "Attributes t_min and t_max must be float object, "+type(time)+" instead"
		self.t_min =t_min
		

class Chromatogram(AbstractTypes):
	"""Chromatogram object """
	
	attributes=('transition')
	
	def __init__(self, t_min = 0., t_max = 0., X=[], Y =[],transition= MS2_trans((0,0))):
		"""
		Constructor
		@transition
		@data points
		"""
		AbstractTypes.__init__(self, t_min, t_max, X, Y)
		self.transition = transition
			
			
	def Plot(self, fill_X=None, fill_Y=None):
		"""
		plotting of the chromatogram
		warning, use Matplotlib library under
		"""
		try:
			from PyQt4 import QtGui, QtCore
			return canvas.canvasUi([self.rt], [self.intensity], fill_X, fill_Y, \
				legends = (self.transition.toString().join(["chrom@", ""]), ), flags= "chrom")
		except ImportError: 
			"Qt not found"
		finally: 
			plot(self.X, self.Y)
	
	def transition(self):
		return self.transition

	def transitionPrecursor(self):
		return self.transition.Precursor()
		
#all attributes are private to avoid possible unwanted modifications


class chromatoPeak(Chromatogram):
	""" 
	chromatographic peak Object
	"""
	
	attributes = ('__area', '__rt', '__iso_cluster', '__frag_cluster', '__idms', '__chromatogram', '__annotation')
	
	def __init__ (self,transition, rt, rtmin, rtmax, area, chromatogram = None,  points=[], iso_clust=ClusterList(),frag_clust=ClusterList()):
		Chromatogram.__init__(rtmin,rtmax,X,Y,transition)
		self.__area = float(area)# area under the peak shape
		self.__rt = float(rt)#rt of the peak (centroided)
		
		#informations about sons signals...clustering
		self.__iso_cluster  = iso_clust
		self.__frag_cluster = frag_clust
		self.__idms = None
		
		# ID chromatogram transition prec/frag
		self.__chromatogram =chromatogram
		self.__annotation =""
	
	
		##################################################################################
	def __eq__(self, p):
		"""
		redefine == for chromatoPeak object
		"""
		if not isinstance(p, chromatoPeak):
			raise TypeError, "comparison with a non-peak object"
		if self.get_rt() == p.get_rt() and self.get_trans()== p.get_trans():
			return True
		return False
		
	def __neq__(self, p):
		"""
		redefine != for chromatoPeak object
		"""
		return not self == p
	
		
	def __hash__(self):
		"""
		to allow chromatoPeak object to be a key in dict
		"""
		return id(self)
	
	def __str__(self):
		"""
		redefine str for chromatoPeak object
		"""
		return '\s'.join([str(transition), str(rt)])
	
	
	
	def set_idms(self, p):
		if not isinstance(p, chromatoPeak):
			raise TypeError, "idms must be a peak"
		self.__idms =p
		
	def idms(self):
		return self.__idms

	
	#compare two peaks among their adducts
	def equal_frag_to(self, l_frag):
		for i, p1 in enumerate(self.get_frag_cluster()._gen_peaks()):
			j=0; found =False
			while not found and j < len(l_frag.get_frag_cluster()):
				if  p1 == l_frag.get_frag_cluster()[j]:
					found =True
					break
				j+=1
			if not found:
				return False
		return True
	
	#merge data
	def merge_data_with(self, peak):
		if not isinstance(data, chromatoPeak):
			raise TypeError, "trying to merge a data with something which is not "
		self.X.extend(peak.getX())
		self.Y.extend(peak.getY())
	
	#method for plotting...
	def Plot(self):
		return canvas_peak.Peak_Widget(self)

	def get_rt(self):
		return self.__rt
		
		
	def get_area (self):
		return self.__area
	
	#handling isotopic cluster
	def get_iso_cluster(self):
		return self.__iso_cluster

	
	def set_iso_cluster(self, cluster):
		for el in cluster:
			if not isinstance(el, chromatoPeak):
				raise TypeError,"At Least One element on one isotopic_cluster is not a peak"
		self.__iso_cluster = cluster
		
	def add_to_iso (self, p):
		if not isinstance(p, chromatoPeak):
			raise TypeError, "One element on one isotopic_cluster is not a peak"
		self.__iso_cluster.add_peak(p)
	
	#handling fragments and adducts of considered peak
	def get_frag_cluster(self):
		return self.__frag_cluster
	
	def set_frag_cluster(self, cluster):
		for el in cluster:
			if not isinstance(el, chromatoPeak):
				raise TypeError,"One element on one isotopic_cluster is not a peak"
		self.__frag_cluster = cluster
		
	def add_to_frag (self, p):
		if not isinstance(p, chromatoPeak):
			raise TypeError, "One element on one isotopic_cluster is not a peak"
		self.__frag_cluster.append(p)
	
	
	def get_sons(self, include_M0 =False):
		if not include_M0:
			return self.__iso_cluster.extend(self.__frag_cluster)
		else:
			l =self.__iso_cluster.extend(self.__frag_cluster)
			l.append(self)
			return l
	
	
	def get_iso_areas(self):
		#for p in self.get_iso_cluster(): yield p.get_area()
		return [p.get_area() for p in self.get_iso_cluster()]
		
	def get_frag_areas(self):
		#for p in self.get_frag_cluster(): yield p.get_area()
		return [p.get_area() for p in self.get_frag_cluster()]
	
	def frag_trans(self):
		for p in self.get_frag_cluster(): yield p.get_trans_prec()
		#return [p.get_trans_prec() for p in self.get_frag_cluster()]
	#		
	def peak_as_tuple(self):
		return self.__transition.get_trans_prec(), self.__rt
		
	def get_chromato(self):
		return self.__chromatogram



class Scan(PySpecBasis):
	"""SCANS useful WHEN PARSING#"""
	
	attributes =('Fields')
	
	def __init__(self, Fields={}):
			self.Fields=Fields
			
	#def _getmassPeak(self):
	#		return massPeak(self.Fields['precursorMz'],self.Fields['basePeakIntensity'])
	
	def rt(self):
			return self.Fields['retentionTime']
	
	def fragment(self): return self.Fields['basePeakMz']
	
	def intensity(self):
			return self.Fields['basePeakIntensity']
	
	def precursor(self):
			return self.Fields['precursorMz']
	
	def scantype(self):
			return self.Fields['scanType']
	
	def byteOrder(self):
			return self.Fields['byteOrder']
	
	def compression(self):
			return self.Fields['compression']
	
	def precision(self):
			return self.Fields['precision']




class Spectrum(AbstractTypes):
	
	"""Pseudo-Spectrum"""
	
	attributes = ()
	
	def __init__(self, t_min=0, t_max=0, X =[], Y=[]):
		AbstractTypes.__init__(self, t_min, t_max, X, Y)

		
	def encode64(self):
		scanbyte=""
		list_scan=self.getPeakasTuple()
		for tupl in list_scan:
				scanbyte+=struct.pack("!ff", *tupl)
		return base64.b64encode(scanbyte)
			
	def decode64 (self, code):
		points = []
		list_tuple = base64.b64decode(code)
		for tupl in list_tuple:
			self.append (Data2D(tupl[0], tupl[1]))

			
	def Plot(self):
			return canvas.canvasUi(self.X,self.Y, \
					 flags ="spectr")
					#(self.transition.toString().join(["chrom@", ""]), )#legends = (str(self.t_min).join(["spectrum at", ""]), )
					
	#*******modif yield		
	def peakAsTuple(self):
		#listPeak=[]
		for point in self:
			yield point.toTuple()
		#	listPeak.append( point.toTuple() )
		#return listPeak

	
	def listMass():
			#listmass=[]
		for point in self:
			yield point.get_x()
					#listmass.append( point.x )
			#return listmass

	def listRT():
			#listrt=[]
		for scan in self.scans:
			yield scan.get_rt()
					 #listrt.append(scan._getrt())
			#return listrt

	def maxMass(self):
		   return max(self._getListMass())

	def minMass(self):
			return min(self._getListMass())

	def mergeWith(self, spectr):
			#def pourcentage erreur sur la masse ?
		if (self.getMinMass(),self.getMaxMass()) == (spectr.getMinMass(),spectr.getMaxMass()):
				message=QtGui.QDialog("Merging two spectrum with the same mass range !")
		
						

class massPeak(Spectrum):
	attributes =()
	def __init__(self, mass, intensity, spectrum_number=None):
		self.mass = mass
		self.intensity = intensity
		self.spectrum_number = spectrum_number
	




class Sample(PySpecBasis):
	"""
	Represents all information found in a xml file
	each attribute is private
	"""
	
	attributes =('__xmlfile', '__header','__spectra', '__chroma', '__peaks', '__mapped_peaks', '__peak_model', '__clust_model', \
				'__id_model')
	
	
	def __init__(self, xmlfile, header="", spectra =[], chroma=[], peaks=RawPeakList(), mapped_peaks =RawPeakList()):
		
		if not xmlfile or not isinstance(xmlfile, str):
			raise TypeError, "Sample's xmlfile must be a non empty string"
			
		self.__xmlfile = xmlfile
		self.__header = header			
		self.__spectra = spectra
		self.__chroma = chroma
		self.__peaks = peaks
		self.__mapped_peaks = mapped_peaks
		
		#peak_model
		self.__peak_model = QtGui.QStandardItemModel(1, 2)
		self.__peak_model.setHorizontalHeaderLabels(["peak","rt","intensity","iso_length","is_validation_iso",\
		"frag_length","is_validation_adduct"])

		#clust_model
		self.__clust_model = QtGui.QStandardItemModel(1, 2)
		self.__clust_model.setHorizontalHeaderLabels( ["peak", "rt", "intensity","isotopic_cluster", "fragments or adducts cluster"])
		
		#identification model
		self.__id_model = QtGui.QStandardItemModel(1, 2)
		self.__id_model.setHorizontalHeaderLabels =["rt", "mass", "mmu", "formulas","score", "KEGG","name", "MEtabolomeJP","name"]
		
		self.__xml_created =False
	
	
	def __spectraGenerator(self):
		"""
		return a spectra generator
		"""
		for spectrum in self.__spectra:
			yield spectrum
			
	def __chromatoGenerator(self):
		"""
		return a chromas generator
		"""
		for chromatogram in self.__chroma:
			yield chromatogram
	
	#table peak
	def peakModel(self):
		return self.__peak_model
		
	def clustModel(self):
		return self.__clust_model
	
	def idModel(self):
		return self.__id_model
		
	def set_mapped_peaks(self, listp):
		if not isinstance(listp, RawPeakList):
			raise  TypeError, "attribute mapped_peaks must be a raw_peak list object", type(listp)
		self.__mapped_peaks =listp
	
	def mappedPeaks(self):
		return self.__mapped_peaks
	
	def addMappedPeak (self, p):
		if not isinstance (p, chromatoPeak):
			raise TypeError, "a mapped_peak must be a chromatoPeak object, ", type(p)
			
	#return chromatogram with specified transition
	def chromatoWithTrans(self, trans):
		for chrom in self._gen_chromas():
			if chrom.get_trans().get_prec() == trans:  #using floor ??? 
				return chrom#yield?
		raise ValueError, "Requested transition does not exist in this file"
	
	def isTransIn (self, trans):
		return trans in [chrom.get_trans().get_prec() for chrom in self.__chroma]
		
		
	def isPeakIn(self, peak):
		return peak in self.__peaks
	"""
	for peak in self.__peaks:
		if peak.get_trans_prec() == mass:
			return True
	return False
	"""
	
	
	#return points of a specified chromatogram		
	def getSliceAtTrans (self, x1, x2, trans):
		if not (isinstance(x1,float) and isinstance(x2, float)):
			raise TypeError, "Slicing Values must be float"
			 
		chrom = self.getChromWithTrans(xmlfile, trans)
		return chrom._getslice (x1, x2)
	
	
	# TODO unify the two next method 	
	def getSpectrAtTimePlot (self, time):
		for spectr in self.__spectra:
			if str(spectr.t_min) == time:
				return spectr
		raise ValueError, "Requested time does not exist in this file"
		
	def getSpectrAtTime (self, time):
		for spectr in self.__spectra:
			if spectr.t_min == time:
				return spectr
		raise ValueError, "Requested time does not exist in this file"
		
	
	def minTransition(self):pass
	
	#return all spectra of a sample as a list
	def Spectra(self):
		return self.__spectra
		
	def setSpectra (self, spectra):
		self.__spectra = spectra
	
	#return all chromatogram of a sample as a list
	def Chromatograms(self):
		return self.__chroma
		
	def setChromatogram (self, chroma):
		self.__chroma = chroma
		
	def peakList(self):
		return self.__peaks
		
	def set_peakList(self, peaks):
		self.__peaks =peaks
		
	def getHeader (self) :
		if self.__header:
			return self.__header
	
	def setHeader (self, string):
		"""
		if not isinstance(string, str):
			raise TypeError, "Sample's header must be a string"
		"""
		self.__header = string

			
	#return the sample 's name
	def XmlFile(self):
		return self.__xmlfile
		




"""Implementing a sample list"""

class ListSample (list):
	
	def __init__(self, samples=[]):
		"""
		Constructor
		"""
		list.__init__(self)
		self.samples =samples
	
	def _gen_samples(self):
		"""
		return the generator
		"""
		for sample in self:
			yield sample
	
	def Append (self, sample):
		"""
		check the instance then simple append
		"""
		if not isinstance(sample, Sample):
			raise TypeError, " ListSamlpe contains only SampleObject"
		
		if not len(sample.getSpectra()) or not len(sample.getChromatogram()):
			raise ValueError, "Sample spectra an chroma must be non empty"
		self.append (sample)
	
	
	def getFiles(self):
		"""
		return the list of the name files
		"""
		return [spl.getXmlFile() for spl in self._gen_samples()]
		
	
	def Sample (self, xmlfile):
		"""return a sample object with specified name""" 	
		for spl in self._gen_samples():
			if spl.getXmlFile()== xmlfile:
				return spl
		raise ValueError, "Requested Sample does not exist in SampleList Object"
	
	
	def SampleTrans (self, xmlfile, trans):
		"""return the chromatogram with the specified transition in a specified file"""
		return self.Sample(xmlfile).getChromWithTrans(trans)
		
	
	def SampleSpectr(self, xmlfile, time):
		"""return the spectrum with the specified min_time in a specified file"""
		return self.Sample(xmlfile).getSpectrTimePlot(time)
		
	def get_samples(self):
		return self
	
	def Remove(self, sample_name):
		"""Remove one sample by its name"""
		i =0
		#for sample in (lambda self.
		while i <len(self):
			if self[i].getXmlFile() == sample_name:
				del self[i]
				return
			i+=1
		raise ValueError, "sample_name not found in the sample list"

		
		
		
		
		
		
############DATABASES

class DataBases(PySpecBasis):
	
	attributes =('__formula', '__mass', '__name')
	
	def __init__(self, mass, formula, name):
		"""
		object for parsers DATABASES
		generic objects
		constructor with formula, mass, name
		"""
		if not isinstance (formula, string):
			raise TypeError, "formula must be a string", type(formula)
		if not isinstance (name, string):
			raise TypeError, "name must be a string", type(mass)
			
		self.__formula = formula
		self.__mass = mass
		self.__name = name
	
	def get_formula(self):
		return self.__formula
		
	def set_formula(self, formula):
		if not isinstance (formula, string):
			raise TypeError, "formula must be a string", type(formula)
		self.__formula =formula
	
	def get_mass(self):
		return self.__mass
		
	def set_mass(self, mass):
		self.__mass = mass
		
	def get_mass (self):
		return self.__mass
		
	def get_name(self):
		return self.__name
		
	def set_name(self, name):
		if not isinstance (formula, string):
			raise TypeError, "formula must be a string", type(formula)
		self.__name =name

		
#object for kegg Database	
class Kegg_data (DataBases):

	attributes =('__entry', '__kegg_base_url')
	#in the kegg database, in the file we got the entry of the compound
	def __init__(self, mass, formula, name, entry ):
		DataBases.__init__(self, mass, formula, name)
		self.__entry = entry
		self.__kegg_base_url ="http://www.genome.jp/dbget-bin/www_bget?cpd:"
		
	def get_url (self):
		url = self.__kegg_base_url + self.__entry
		return QtCore.QUrl(url)
	
	def url_to_string(self):
		return self.get_url().toString()
		
	
#object for met_jp database
class Met_jp_data (DataBases):
	attributes =()
	# do not have any entry
	def __init__(self, mass, formula, name):
		DataBases.__init__(self, mass, formula, name)
		
class BioCyc(DataBases):
	
	attributes =('__synonyms', '__kegg_entry', '__biocyc_entry', '__smiles')
	
	def __init (self,  mass,  formula,  name,  synonyms,  kegg_entry,  biocyc_entry,  smiles):
		DataBases.__init__(self,  mass,  formula,  name)
		
		if not isinstance(synonyms,  list):
			raise TypeError, "Synonyms must be list type instead of",  type(synonyms)
		self.__synonyms =synonyms
		
		if not isinstance (kegg_entry,  str) and not isinstance (biocyc_entry,  str):
			raise TypeError,  "Databases entries must string instead of",  type(kegg_entry)
		self.__kegg_entry =kegg_entry
		self.__biocyc_entry =biocyc_entry
		
		self.__smiles =smiles
	
	def get_kegg_entry(self):
		return self.__kegg_entry
		
	def get_biocyc_entry(self):
		return self.__biocyc_entry
		
	def get_smiles(self):
		return self.__smiles
		
	
########################################################################## 
##########FORMULAS OBJECT

 #object for handling smiles data
class smiles(str):
	
	
	"""C([O-])(=O)C1(=CC=C2(C(=C1)C(=O)C3(C(C2=O)=CC=CC=3)))"""
	
	def __init__(self, smiles_formula, mass, formula):
		str.__init__(self, smiles_formula)
		#transformation lower to upper case
		self =[symbol.upper() for symbol in self if symbol.islower()]
		
		self.__mass = mass
		self.__formula = formula
		
		#carboxylix function: C(=O)O, C([O-])(=O), C(=O)[O-]
		self.carboxy_pattern_list =['C(=O)O', 'C([O-])(=O)', 'C(=O)[O-]']
		#alcohol function:
		self.alcohol_pattern = ['CO']
		#Phosphate
		self.phospahte_pattern =[]
		
	def looking_functions_pattern(self):
		#first carboxylic functions
		nb_carboxy =0
		nb_OH =0
		
		for pattern in self.carboxy_pattern_list:
			reg_expression = re.compile(pattern)
			if reg_expression.match(self):
				nb_carboxy+=1
				
		for pattern in self.alcohol_pattern:
			reg_exp =re.compile(pattern)
			if reg_exp.match(line):
				nb_OH+=1
			
		return nb_carboxy, nb_OH
	
	def get_smiles(self):
		return self
	
	def get_formula(self):
		return self.__formula
		
	def get_mass(self):
		return self.__mass
		
		



class Element(PySpecBasis):
	"""represent an Element example: C, O, H"""
	
	attributes =('symbol', '__mass', '__abundoncy', '__name')
	
	def __init__(self, mass, abundoncy, symbol, name="" ):
		if not isinstance(symbol, str):
			raise TypeError, "Symbol must be a String"
			
		self.symbol =symbol
		self.__mass =mass
		self.__abundoncy =abundoncy
		self.__name =name
		
	def get_symbol (self):
		return self.symbol
		
	def get_abundoncy(self):
		return self.__abundoncy
	
	def full_name(self):
		return self.__name



#alphabet with all the abundoncies
class Alphabet(list):
	"""represent the used alphabet to find metabolites, this is a list of element"""
	
	def __init__(self, el =[]):
		list.__init__(self, el)
	
	#you can redefine the __str__ static method

	def __str__(self):
		return [el.get_symbol() for el in self]
		
	def add_element(self, element):
		if not isinstance(element, Element):
			raise TypeError,"Element must be an Element object"
		self.append(element)
		
	def get_alphabet(self):
		return self
	
	def is_in (self, symbol):
		return symbol in str(self)
		#if symbol not in self.toString():
		#	return False
		#return True
		
	def element_with_symbol (self, symbol):
		for el in self:
			if el.get_symbol() == symbol:
				return el
		return None

		
#make something more generic not asking C, H, N but one element in parameters
class Formula (dict) :
	"""Representation of element,metabolite"""

	#CHNOPS
	def __init__(self, formula, mass, alphabet=Alphabet()):
		#change the formula(string) to object dict
		self.__alphabet = alphabet
		self.__formula = formula
		
		#will ba list of string (name)
		self.__identifiers =[] 
		
		reg = re.compile('([A-Z]|[A-Z][a-z])(\d+)')
		for splitted in self.__formula.split('.'):
			if reg.match(splitted):
				for element in self.__alphabet:
					if element.get_symbol() == reg.match(splitted).group(1):
						self[element] = int(reg.match(splitted).group(2))
		
		self.__mass = mass 
		self.score = 0
		self.__valid =True
		
	def set_identification(self,  names):
		if not isinstance (names,  list):
			raise TypeError,  "names of a formula must be a list", type(names)
		self.__identifiers = names
		
	def get_identifiers (self):
		return self.__identifiers
		
	def is_identified(self):
		return not len(self.__identifiers) == 0
		
		
	def set_valid(self, valid):
		self.__valid ==valid
	def get_valid(self):
		return self.__valid

	def  set_score(self, value):
		self.score =value
		
	def get_score(self):
		return self.score
	
	def get_mass (self):
		return self.__mass
		
	def get_number_of (self, symbol):
		if self.__alphabet.is_in(symbol):
			for element in self.keys():
				if symbol == element.get_symbol():
					return self[element]
		else:
			raise ValueError, "Given symbol not in alphabet"
			print self.__alphabet.toString(), symbol
		
	def getFormula(self):
		return self.__formula
	
	def toString(self):
		formula =""
		for element in sorted(self.keys(), key =attrgetter('symbol')):#, key= lambda element:element.symbol
			formula += element.get_symbol()
			if self[element] != 1:
				formula+=str(self[element])
		return formula
		
	def getAlphabet (self):
		if self.__alphabet:
			return self.__alphabet
		
	def getmass(self):
		return self.__mass
	
	def pattern_generation(self):
		return processing.PatternCalculation(abundoncy, self)





class Annotation(PySpecBasis):
	"""Annotation object"""
	
	attributes =('chromatogram', 'point', 'name')
	
	def __init__(self, chromatogram, point, name):
		QtCore.QObject.__init__(self)
		self.chromatogram =chromatogram
		self.point = point
		self.name = name
		
	def chromatogram(self):
		return self.chromatogram
		
	def point(self):
		return self.point
	
	def name(self):
		return self.name
	
	def is_name_in_database(self):pass
	
	

	
	


